package ai.fedml.edge;

import org.junit.Test;

import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Random;

import ai.fedml.edge.utils.ObfuscatedString;

final class ObfuscatedStringTest {
    private static final Random SECURE_RANDOM = new SecureRandom();

    /**
     * @param bytes The array containing the bytes to decode in little endian order.
     * @param off   The offset of the bytes in the array.
     * @return The decoded long value.
     */
    private static long toLong(final byte[] bytes, int off) {
        final int end = Math.min(bytes.length, off + 8);
        long l = 0;
        for (int i = end; --i >= off; ) {
            l <<= 8;
            l |= bytes[i] & 0xFF;
        }
        return l;
    }

    private static void appendHexLiteral(final StringBuffer sb, final long l) {
        // obfuscation futile - too short
        sb.append("0x");
        sb.append(Long.toHexString(l).toUpperCase());
        sb.append('L');
    }

    public static String obfuscate(final String s) {
        // Any string literal used in this method is represented as an
        // ObfuscatedString unless it's no longer than two characters.
        // This should help to prevent location of this class in the obfuscated
        // code generated by ProGuard.
        if (-1 != s.indexOf(0)) {
            // => throw "Null characters are not allowed!";
            throw new IllegalArgumentException(new ObfuscatedString(new long[]{
                    0x241005931110FC70L, 0xDCD925A88EAD9F37L, 0x19ADA1C861E2A85DL,
                    0x9A5948E700FCAD8AL, 0x2E11C83A72441DE2L
            }).toString());
        }

        // Obtain the string as a sequence of UTF-8 encoded bytes.
        final byte[] encoded = s.getBytes(StandardCharsets.UTF_8);
        // seed strength is effectively 48 bits
        final long seed = SECURE_RANDOM.nextLong();
        // Create and seed a Pseudo Random Number Generator (PRNG) with a random long number.
        final Random prng = new Random();
        // randomly seeded
        prng.setSeed(seed);

        // Construct a StringBuffer to hold the generated code and append the
        // seed as the first element of the encoded array of longs.
        // The value is represented in hexadecimal in order to keep the string
        // representation as short as possible.
        final StringBuffer code = new StringBuffer(new ObfuscatedString(new long[]{
                0xA28E32BB0D3E394EL, 0xF842D1C94E549EECL, 0x7D07DFF01F907E4L,
                0x4E0BDE791ECD467CL, 0xDFF389B58DA3E44FL, 0x2477FAED0CE62C79L
        }).toString());
        // => "new ObfuscatedString(new long[] {";
        appendHexLiteral(code, seed);

        final int length = encoded.length;
        for (int i = 0; i < length; i += 8) {
            final long key = prng.nextLong();
            // Compute the value of the next array element as an obfuscated
            // version of the next eight bytes of the UTF8 encoded string.
            final long obfuscated = toLong(encoded, i) ^ key;
            code.append(", ");
            appendHexLiteral(code, obfuscated);
        }

        // => "}).toString() /* => \"";
        code.append(new ObfuscatedString(new long[]{
                0x4200B7AD6FFFF546L, 0x9B822E95FE73769DL, 0x23C2800C6CACFCE3L, 0x21C30B492D9AEF99L
        }));

        // Append the original string to the generated code comment, properly escaping quotation marks and backslashes.
        code.append(s.replaceAll("\\\\", new ObfuscatedString(new long[]{
                /* => "\\\\\\\\" */
                0x6D2C680D49523A01L, 0xB932F1DBD19E82CEL}).toString())
                .replaceAll("\"", new ObfuscatedString(new long[]{
                        /* => "\\\\\"" */
                        0x85E9D53EF7A9324BL, 0xB05BD65C9F19DE07L}).toString()));

        code.append(new ObfuscatedString(new long[]{
                // => "\" */"
                0xC54FFF0621E7D107L, 0x194EAD468C6FCF93L
        }));
        return code.toString();
    }

    @Test
    void obfuscate() {
        String key = "X-Sign-Signature";
        String obfuscateKey = obfuscate(key);
        System.out.println(obfuscateKey);
    }

    @Test
    void testToString() {
        ObfuscatedString obfuscatedString = new ObfuscatedString(new long[]{0xE2BE96BEEDEC6530L, 0x3796829F9366063DL, 0x373AE7940877B1E0L});
        System.out.println(obfuscatedString);
    }

    public static void main(String[] args) {
        String key = "X-Sign-Signature";
        ObfuscatedString obfuscatedString = new ObfuscatedString(new long[]{0xE2BE96BEEDEC6530L, 0x3796829F9366063DL, 0x373AE7940877B1E0L});
        String obfuscateKey = obfuscate(key);
        System.out.println(obfuscateKey);
    }
}
